package com.bjy.qa.util.fileparser;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.bjy.qa.entity.functionaltest.Api;
import com.bjy.qa.entity.functionaltest.ApiParam;
import com.bjy.qa.enumtype.CatalogType;
import com.bjy.qa.enumtype.ParamKind;
import com.bjy.qa.exception.MyException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.rslai.commons.util.postman.parser.collection.v2_1.PosBody;
import com.rslai.commons.util.postman.parser.collection.v2_1.PosCollection;
import com.rslai.commons.util.postman.parser.collection.v2_1.PosCollectionParser;
import com.rslai.commons.util.postman.parser.collection.v2_1.PosResponseItem;
import com.rslai.commons.util.postman.parser.collection.v2_1.body.PosFormDataItem;
import com.rslai.commons.util.postman.parser.collection.v2_1.body.PosUrlEncodedItem;
import com.rslai.commons.util.postman.parser.collection.v2_1.cookie.PosCookieItem;
import com.rslai.commons.util.postman.parser.collection.v2_1.header.PosHeaderItem;
import com.rslai.commons.util.postman.parser.collection.v2_1.item.PosItem;
import com.rslai.commons.util.postman.parser.collection.v2_1.url.PosQueryItem;
import com.rslai.commons.util.postman.parser.collection.v2_1.url.PosUrl;
import com.saasquatch.jsonschemainferrer.DefaultPolicies;
import com.saasquatch.jsonschemainferrer.JsonSchemaInferrer;
import com.saasquatch.jsonschemainferrer.SpecVersion;
import org.apache.commons.lang.StringUtils;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *  Postman 格式文件解析器，将 Postman 格式文件解析为 Api 对象
 */
public class PostmanParser {
    private static final ObjectMapper mapper = new ObjectMapper();
    private static final JsonSchemaInferrer inferrer = JsonSchemaInferrer.newBuilder()
            .setSpecVersion(SpecVersion.DRAFT_04)
//            .addFormatInferrers(FormatInferrers.email(), FormatInferrers.ip()) // 校验器（格式推断器：邮件地址、ip 地址）
//            .setAdditionalPropertiesPolicy(AdditionalPropertiesPolicies.notAllowed()) // 设置附加属性策略（AdditionalPropertiesPolicies） 。notAllowed：不允许附加属性
//            .setRequiredPolicy(RequiredPolicies.nonNullCommonFields()) // 设置必填项（Required） 。nonNullCommonFields：非空字段都会被设置为必填项
//            .addEnumExtractors(EnumExtractors.validEnum(java.time.Month.class), EnumExtractors.validEnum(java.time.DayOfWeek.class)) // 枚举提取器
            .setDefaultPolicy(DefaultPolicies.useFirstSamples())
            .build();

    /**
     * 将 Postman 格式文件解析为 Api 对象
     * @param filePath Postman 格式文件路径
     * @param projectId 项目 id
     * @return
     */
    public Map<String, List<Api>> toApi(String filePath, Long projectId) {
        Map<String, List<Api>> apis = new HashMap<>();
        try {
            PosCollection postman = new PosCollectionParser().parser(new File(filePath)); // 得到 postman 对象
            if (postman.info.name == null && postman.info._postman_id == null && postman.info.schema == null) {
                throw new MyException("非 postman 文件格式！");
            }

            String title = postman.info.name; // 获取 api name

            // 遍历 posItems，解析 api
            if (postman.item != null) {
                for (PosItem posItem : postman.item) {
                    recursivePostmanItem(projectId, title, posItem, apis); // 递归解析 postman item
                }
            }
        } catch (Exception e) {
            throw new MyException("导入接口定义 - postman 格式失败：解析 postman 文件失败。" + e.getMessage());
        }
        return apis;
    }

    /**
     * 递归解析 postman item
     * @param projectId 项目 ID
     * @param catalogName 分类名称
     * @param posItem postman item
     * @param apis api 列表
     * @return
     */
    private boolean recursivePostmanItem(Long projectId, String catalogName, PosItem posItem, Map<String, List<Api>> apis) {
        if (posItem.item == null) { // 如果 没有子元素（item 为空），则是一个 api 定义，直接添加到 apis 中
            parserGetPostman(projectId, catalogName, posItem, apis); // 解析 get 请求
            parserPostPostman(projectId, catalogName, posItem, apis); // 解析 post 请求
            parserPutPostman(projectId, catalogName, posItem, apis); // 解析 put 请求
            parserDeletePostman(projectId, catalogName, posItem, apis); // 解析 delete 请求
            parserPatchPostman(projectId, catalogName, posItem, apis); // 解析 patch 请求
        } else { // 如果 有子元素（item 不为空），循环解析
            for (PosItem tmpPosItem : posItem.item) {
                if (posItem.item == null) { // 如果 没有子元素（item 为空），则是一个 api 定义，直接添加到 apis 中
                    parserGetPostman(projectId, catalogName, posItem, apis); // 解析 get 请求
                    parserPostPostman(projectId, catalogName, posItem, apis); // 解析 post 请求
                    parserPutPostman(projectId, catalogName, posItem, apis); // 解析 put 请求
                    parserDeletePostman(projectId, catalogName, posItem, apis); // 解析 delete 请求
                    parserPatchPostman(projectId, catalogName, posItem, apis); // 解析 patch 请求
                } else { // 如果 有子元素（item 不为空），则递归解析
                    String currentCatalogName = "";
                    if (posItem.name != null) {
                        currentCatalogName = "/" + posItem.name;
                    }
                    recursivePostmanItem(projectId, catalogName + currentCatalogName, tmpPosItem, apis);
                    continue;
                }
            }
        }
        return true;
    }

    /**
     * 解析 host - postman
     * @param posUrl postman url 对象
     * @return
     */
    private String parserHostPostman(PosUrl posUrl) {
        String host = "";

        if (posUrl.protocol != null) {
            host = posUrl.protocol + "://";
        }

        if (posUrl.host != null) {
            for (int i = 0; i < posUrl.host.size(); i++) {
                if (i == posUrl.host.size() - 1) {
                    host = host + posUrl.host.get(i);
                } else {
                    host = host + posUrl.host.get(i) + ".";
                }
            }
        }
        return host;
    }

    /**
     * 解析 uri - postman
     * @param posUrl postman url 对象
     * @return
     */
    private String parserUriPostman(PosUrl posUrl) {
        String uri = "";
        if (posUrl.path != null) {
            for (String tmp : posUrl.path) {
                uri = uri + "/" + tmp;
            }
        }

        if (StringUtils.isBlank(uri)) {
            uri = "/";
        }
        return uri;
    }

    /**
     * 解析 api 的基础 信息（name、host、uri、projectId），并返回 api 对象
     * @param projectId 项目id
     * @param host host
     * @param uri uri
     * @param posItem postman item 对象
     * @return
     */
    private Api parserBasePostman(Long projectId, String host, String uri, PosItem posItem) {
        Api api = new Api();
        api.setProjectId(projectId);
        api.setHost(host);
        api.setUri(uri);
        api.setType(CatalogType.INTERFACE_HTTP.getValue());
        api.setApiParams(new ArrayList<>());

        // 设置 api 名称
        String name = posItem.name;
        if (StringUtils.isEmpty(name)) {
            name = posItem.description;
        }
        if (name.length() > 255) {
            name = name.substring(0, 255);
        }
        api.setName(name);

        return api;
    }

    /**
     * post type 转 json type
     * @param type postman type
     * @return
     */
    private String postmanType2JsonType(String type) {
        switch (type) {
            case "default":
                return "string";
            default:
                return "string";
        }
    }

    /**
     * 解析 responses(assert) 参数 - postman
     * @param posItem postman item 对象
     * @return
     */
    private ApiParam parserResponsesPostman(PosItem posItem) {
        ApiParam apiParam = new ApiParam();
        apiParam.setKind(ParamKind.KIND_ASSERT.getValue());
        apiParam.setName(ParamKind.KIND_ASSERT.getName());
        apiParam.setDes("{\"type\":\"object\",\"properties\":{}}");
        apiParam.setExample("{}");

        if (posItem.response != null && posItem.response.size() > 0) {
            PosResponseItem posResponse = posItem.response.get(0);
            if (posResponse.header != null) {
                for (PosHeaderItem posHeaderItem : posResponse.header) {
                    // 只给 response 的 content-type=json 的设置 "响应"。其他类型忽略，不设置
                    if ("content-type".equalsIgnoreCase(posHeaderItem.key) && posHeaderItem.value.toLowerCase().indexOf("application/json") >= 0) {
                        if (StringUtils.isNotEmpty(posResponse.body)) {
                            try {
                                ObjectNode inferForSample = inferrer.inferForSample(mapper.readTree(posResponse.body));
                                apiParam.setDes(inferForSample.toString());
                            } catch (JsonProcessingException e) {
                                throw new RuntimeException(e);
                            }
                        }
                        break;
                    }
                }
            }
        }

        return apiParam;
    }

    /**
     * 解析 body 参数 - postman
     * @param posItem postman item 对象
     * @param api api 对象
     * @return
     */
    private ApiParam parserBodyPostman(PosItem posItem, Api api) {
        ApiParam apiParam = new ApiParam();
        apiParam.setKind(ParamKind.KIND_BODY.getValue());
        apiParam.setName(ParamKind.KIND_BODY.getName());
        apiParam.setDes("{\"type\":\"object\",\"properties\":{}}");
        apiParam.setExample("{}");

        api.setExtra1("FORM"); // 默认设置为 form 类型，后边遇到 json 类型会覆盖

        if (posItem.request.body != null) {
            PosBody posBody = posItem.request.body;
            posBody.mode = posBody.mode.toLowerCase();
            switch (posBody.mode) {
                case "raw":
                    if (posBody.options != null && posBody.options.getJSONObject("raw") != null && posBody.options.getJSONObject("raw").getString("language") != null) {
                        switch (posBody.options.getJSONObject("raw").getString("language")) {
                            case "json":
                                api.setExtra1("JSON"); // 设置为 json 类型（覆盖）
                                if (!StringUtils.isBlank(posItem.request.body.raw)) {
                                    try {
                                        ObjectNode inferForSample = inferrer.inferForSample(mapper.readTree(posItem.request.body.raw));
                                        apiParam.setDes(inferForSample.toString());
                                    } catch (JsonProcessingException e) {
                                        throw new RuntimeException(e);
                                    }
                                }
                                break;
                            case "text":
                            case "javascript":
                            case "html":
                            case "xml":
                            default:
                                throw new MyException("body raw 类型中暂不支持的 language：" + posBody.options.getJSONObject("raw").getString("language"));
                        }
                    }
                    break;
                case "urlencoded":
                    if (posBody.urlencoded != null && posBody.urlencoded.size() > 0) {
                        com.alibaba.fastjson.JSONObject schema = JSON.parseObject("{\"type\":\"object\",\"properties\":{}}");

                        Map<String, Map<String, String>> properties = new HashMap<>();
                        for (PosUrlEncodedItem parameter : posItem.request.body.urlencoded) {
                            if (parameter.disabled == null || (parameter.disabled != null && !parameter.disabled)) {
                                Map<String, String> property = new HashMap<>();
                                property.put("type", postmanType2JsonType(parameter.type));
                                property.put("description", parameter.description);
                                if (StringUtils.isNotEmpty(parameter.value)) {
                                    property.put("default", parameter.value);
                                }

                                properties.put(parameter.key, property);
                            }
                        }

                        if (properties.size() != 0) {
                            schema.getObject("properties", Map.class).putAll(properties);
                        }

                        apiParam.setDes(com.alibaba.fastjson.JSONObject.toJSONString(schema));
                    }
                    break;
                case "formdata":
                    if (posBody.formdata != null && posBody.formdata.size() > 0) {
                        com.alibaba.fastjson.JSONObject schema = JSON.parseObject("{\"type\":\"object\",\"properties\":{}}");

                        Map<String, Map<String, String>> properties = new HashMap<>();
                        for (PosFormDataItem parameter : posItem.request.body.formdata) {
                            if (parameter.disabled == null || (parameter.disabled != null && !parameter.disabled)) {
                                Map<String, String> property = new HashMap<>();
                                property.put("type", postmanType2JsonType(parameter.type));
                                property.put("description", parameter.description);
                                if (StringUtils.isNotEmpty(parameter.value)) {
                                    property.put("default", parameter.value);
                                }

                                properties.put(parameter.key, property);
                            }
                        }

                        if (properties.size() != 0) {
                            schema.getObject("properties", Map.class).putAll(properties);
                        }

                        apiParam.setDes(com.alibaba.fastjson.JSONObject.toJSONString(schema));
                    }
                    break;
                case "file":
                case "graphql":
                default:
                    throw new MyException("暂不支持的 body 类型：" + posBody.mode);
            }
        }

        return apiParam;
    }

    /**
     * 解析 cookie 参数 - postman
     * @param posItem postman item 对象
     * @return
     */
    private ApiParam parserCookiePostman(PosItem posItem) {
        com.alibaba.fastjson.JSONObject schema = JSON.parseObject("{\"type\":\"object\",\"properties\":{}}");
        Map<String, Map<String, String>> properties = new HashMap<>();

        if (posItem.response != null && posItem.response.size() > 0 && posItem.response.get(0).cookie != null) {
            for (PosCookieItem parameter : posItem.response.get(0).cookie) {
                Map<String, String> propertie = new HashMap<>();

                propertie.put("type", postmanType2JsonType(""));
                propertie.put("default", parameter.value);

                properties.put(parameter.name, propertie);
            }
        }

        if (properties.size() != 0) {
            schema.getObject("properties", Map.class).putAll(properties);
        }

        ApiParam apiParam = new ApiParam();
        apiParam.setKind(ParamKind.KIND_COOKIE.getValue());
        apiParam.setName(ParamKind.KIND_COOKIE.getName());
        apiParam.setDes(JSON.toJSONString(schema, SerializerFeature.DisableCircularReferenceDetect));
        apiParam.setExample("{}");

        return apiParam;
    }

    /**
     * 解析 header 参数 - postman
     * @param posItem postman item 对象
     * @return
     */
    private ApiParam parserHeadPostman(PosItem posItem) {
        com.alibaba.fastjson.JSONObject schema = JSON.parseObject("{\"type\":\"object\",\"properties\":{}}");
        Map<String, Map<String, String>> properties = new HashMap<>();

        if (posItem.request.header != null) {
            for (PosHeaderItem parameter : posItem.request.header) {
                Map<String, String> propertie = new HashMap<>();

                propertie.put("type", postmanType2JsonType(parameter.type));
                propertie.put("default", parameter.value);
                if (parameter.description != null) {
                    propertie.put("description", parameter.description);
                }

                properties.put(parameter.key, propertie);
            }
        }

        if (properties.size() != 0) {
            schema.getObject("properties", Map.class).putAll(properties);
        }

        ApiParam apiParam = new ApiParam();
        apiParam.setKind(ParamKind.KIND_HEADER.getValue());
        apiParam.setName(ParamKind.KIND_HEADER.getName());
        apiParam.setDes(JSON.toJSONString(schema, SerializerFeature.DisableCircularReferenceDetect));
        apiParam.setExample("{}");

        return apiParam;
    }

    /**
     * 解析 query（url param） 参数 - postman
     * @param posItem postman item 对象
     * @return
     */
    private ApiParam parserParamsPostman(PosItem posItem) {
        com.alibaba.fastjson.JSONObject schema = JSON.parseObject("{\"type\":\"object\",\"properties\":{}}");
        Map<String, Map<String, String>> properties = new HashMap<>();

        if (posItem.request.url.query != null) {
            for (PosQueryItem parameter : posItem.request.url.query) {
                if (parameter.disabled == null || (parameter.disabled != null && !parameter.disabled)) {
                    Map<String, String> propertie = new HashMap<>();

                    propertie.put("type", postmanType2JsonType(""));
                    propertie.put("default", parameter.value);
                    if (parameter.description != null) {
                        propertie.put("description", parameter.description);
                    }

                    properties.put(parameter.key, propertie);
                }
            }
        }

        if (properties.size() != 0) {
            schema.getObject("properties", Map.class).putAll(properties);
        }

        ApiParam apiParam = new ApiParam();
        apiParam.setKind(ParamKind.KIND_URL_PARAM.getValue());
        apiParam.setName(ParamKind.KIND_URL_PARAM.getName());
        apiParam.setDes(JSON.toJSONString(schema, SerializerFeature.DisableCircularReferenceDetect));
        apiParam.setExample("{}");

        return apiParam;
    }

    /**
     * 解析 patch 请求 - postman
     * @param projectId 项目id
     * @param catalogName catalog 名称
     * @param posItem postman item 对象
     * @param apis api 列表
     */
    private void parserPatchPostman(Long projectId, String catalogName, PosItem posItem, Map<String, List<Api>> apis) {
        if ("PATCH".equalsIgnoreCase(posItem.request.method)) {
            String host = parserHostPostman(posItem.request.url);
            String uri = parserUriPostman(posItem.request.url);

            Api api = parserBasePostman(projectId, host, uri, posItem);
            api.setMethod("PATCH");

            api.getApiParams().add(parserHeadPostman(posItem)); // 解析 head 参数
            api.getApiParams().add(parserParamsPostman(posItem)); // 解析 query（url param） 参数
            api.getApiParams().add(parserBodyPostman(posItem, api)); // 解析 body 参数
            api.getApiParams().add(parserCookiePostman(posItem)); // 解析 cookie 参数
            api.getApiParams().add(parserResponsesPostman(posItem)); // 解析 responses(assert) 参数

            // 将解析出来的 api 添加到 api map 中
            List<Api> apiList = apis.get(catalogName);
            if (apiList == null) {
                apiList = new ArrayList<>();
                apis.put(catalogName, apiList);
            }
            apiList.add(api);
        }
    }

    /**
     * 解析 delete 请求 - postman
     * @param projectId 项目id
     * @param catalogName catalog 名称
     * @param posItem postman item 对象
     * @param apis api 列表
     */
    private void parserDeletePostman(Long projectId, String catalogName, PosItem posItem, Map<String, List<Api>> apis) {
        if ("DELETE".equalsIgnoreCase(posItem.request.method)) {
            String host = parserHostPostman(posItem.request.url);
            String uri = parserUriPostman(posItem.request.url);

            Api api = parserBasePostman(projectId, host, uri, posItem);
            api.setMethod("DELETE");

            api.getApiParams().add(parserHeadPostman(posItem)); // 解析 head 参数
            api.getApiParams().add(parserParamsPostman(posItem)); // 解析 query（url param） 参数
            api.getApiParams().add(parserBodyPostman(posItem, api)); // 解析 body 参数
            api.getApiParams().add(parserCookiePostman(posItem)); // 解析 cookie 参数
            api.getApiParams().add(parserResponsesPostman(posItem)); // 解析 responses(assert) 参数

            // 将解析出来的 api 添加到 api map 中
            List<Api> apiList = apis.get(catalogName);
            if (apiList == null) {
                apiList = new ArrayList<>();
                apis.put(catalogName, apiList);
            }
            apiList.add(api);
        }
    }

    /**
     * 解析 put 请求 - postman
     * @param projectId 项目id
     * @param catalogName catalog 名称
     * @param posItem postman item 对象
     * @param apis api 列表
     */
    private void parserPutPostman(Long projectId, String catalogName, PosItem posItem, Map<String, List<Api>> apis) {
        if ("PUT".equalsIgnoreCase(posItem.request.method)) {
            String host = parserHostPostman(posItem.request.url);
            String uri = parserUriPostman(posItem.request.url);

            Api api = parserBasePostman(projectId, host, uri, posItem);
            api.setMethod("PUT");

            api.getApiParams().add(parserHeadPostman(posItem)); // 解析 head 参数
            api.getApiParams().add(parserParamsPostman(posItem)); // 解析 query（url param） 参数
            api.getApiParams().add(parserBodyPostman(posItem, api)); // 解析 body 参数
            api.getApiParams().add(parserCookiePostman(posItem)); // 解析 cookie 参数
            api.getApiParams().add(parserResponsesPostman(posItem)); // 解析 responses(assert) 参数

            // 将解析出来的 api 添加到 api map 中
            List<Api> apiList = apis.get(catalogName);
            if (apiList == null) {
                apiList = new ArrayList<>();
                apis.put(catalogName, apiList);
            }
            apiList.add(api);
        }
    }

    /**
     * 解析 post 请求 - postman
     * @param projectId 项目id
     * @param catalogName catalog 名称
     * @param posItem postman item 对象
     * @param apis api 列表
     */
    private void parserPostPostman(Long projectId, String catalogName, PosItem posItem, Map<String, List<Api>> apis) {
        if ("POST".equalsIgnoreCase(posItem.request.method)) {
            String host = parserHostPostman(posItem.request.url);
            String uri = parserUriPostman(posItem.request.url);

            Api api = parserBasePostman(projectId, host, uri, posItem);
            api.setMethod("POST");

            api.getApiParams().add(parserHeadPostman(posItem)); // 解析 head 参数
            api.getApiParams().add(parserParamsPostman(posItem)); // 解析 query（url param） 参数
            api.getApiParams().add(parserBodyPostman(posItem, api)); // 解析 body 参数
            api.getApiParams().add(parserCookiePostman(posItem)); // 解析 cookie 参数
            api.getApiParams().add(parserResponsesPostman(posItem)); // 解析 responses(assert) 参数

            // 将解析出来的 api 添加到 api map 中
            List<Api> apiList = apis.get(catalogName);
            if (apiList == null) {
                apiList = new ArrayList<>();
                apis.put(catalogName, apiList);
            }
            apiList.add(api);
        }
    }

    /**
     * 解析 get 请求 - postman
     * @param projectId 项目id
     * @param catalogName catalog 名称
     * @param posItem postman item 对象
     * @param apis api 列表
     */
    private void parserGetPostman(Long projectId, String catalogName, PosItem posItem, Map<String, List<Api>> apis) {
        if ("GET".equalsIgnoreCase(posItem.request.method)) {
            String host = parserHostPostman(posItem.request.url);
            String uri = parserUriPostman(posItem.request.url);

            Api api = parserBasePostman(projectId, host, uri, posItem); // 生成 api
            api.setMethod("GET");

            api.getApiParams().add(parserHeadPostman(posItem)); // 解析 head 参数
            api.getApiParams().add(parserParamsPostman(posItem)); // 解析 query（url param） 参数
            api.getApiParams().add(parserBodyPostman(posItem, api)); // 解析 body 参数
            api.getApiParams().add(parserCookiePostman(posItem)); // 解析 cookie 参数
            api.getApiParams().add(parserResponsesPostman(posItem)); // 解析 responses(assert) 参数

            // 将解析出来的 api 添加到 api map 中
            List<Api> apiList = apis.get(catalogName); // 得到 catalog 名称用作 api 的 key
            if (apiList == null) {
                apiList = new ArrayList<>();
                apis.put(catalogName, apiList);
            }
            apiList.add(api);
        }
    }
}
